#ifndef THINK_THINK_TEMPLATE_TAGLIB_H
#define THINK_THINK_TEMPLATE_TAGLIB_H

static char* think_template_taglib =
	"\n"
	"// +----------------------------------------------------------------------\n"
	"// | ThinkPHP [ WE CAN DO IT JUST THINK ]\n"
	"// +----------------------------------------------------------------------\n"
	"// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.\n"
	"// +----------------------------------------------------------------------\n"
	"// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n"
	"// +----------------------------------------------------------------------\n"
	"// | Author: liu21st <liu21st@gmail.com>\n"
	"// +----------------------------------------------------------------------\n"
	"\n"
	"namespace think\\template;\n"
	"\n"
	"use think\\Exception;\n"
	"\n"
	"/**\n"
	" * ThinkPHP标签库TagLib解析基类\n"
	" * @category   Think\n"
	" * @package  Think\n"
	" * @subpackage  Template\n"
	" * @author    liu21st <liu21st@gmail.com>\n"
	" */\n"
	"class TagLib\n"
	"{\n"
	"\n"
	"    /**\n"
	"     * 标签库定义XML文件\n"
	"     * @var string\n"
	"     * @access protected\n"
	"     */\n"
	"    protected $xml  = \'\';\n"
	"    protected $tags = []; // 标签定义\n"
	"    /**\n"
	"     * 标签库名称\n"
	"     * @var string\n"
	"     * @access protected\n"
	"     */\n"
	"    protected $tagLib = \'\';\n"
	"\n"
	"    /**\n"
	"     * 标签库标签列表\n"
	"     * @var array\n"
	"     * @access protected\n"
	"     */\n"
	"    protected $tagList = [];\n"
	"\n"
	"    /**\n"
	"     * 标签库分析数组\n"
	"     * @var array\n"
	"     * @access protected\n"
	"     */\n"
	"    protected $parse = [];\n"
	"\n"
	"    /**\n"
	"     * 标签库是否有效\n"
	"     * @var bool\n"
	"     * @access protected\n"
	"     */\n"
	"    protected $valid = false;\n"
	"\n"
	"    /**\n"
	"     * 当前模板对象\n"
	"     * @var object\n"
	"     * @access protected\n"
	"     */\n"
	"    protected $tpl;\n"
	"\n"
	"    protected $comparison = [\' nheq \' => \' !== \', \' heq \' => \' === \', \' neq \' => \' != \', \' eq \' => \' == \', \' egt \' => \' >= \', \' gt \' => \' > \', \' elt \' => \' <= \', \' lt \' => \' < \'];\n"
	"\n"
	"    /**\n"
	"     * 构造函数\n"
	"     * @access public\n"
	"     * @param \\stdClass $template 模板引擎对象\n"
	"     */\n"
	"    public function __construct($template)\n"
	"    {\n"
	"        $this->tpl = $template;\n"
	"    }\n"
	"\n"
	"    /**\n"
	"     * 按签标库替换页面中的标签\n"
	"     * @access public\n"
	"     * @param  string $content 模板内容\n"
	"     * @param  string $lib 标签库名\n"
	"     * @return void\n"
	"     */\n"
	"    public function parseTag(&$content, $lib = \'\')\n"
	"    {\n"
	"        $tags = [];\n"
	"        $lib  = $lib ? strtolower($lib) . \':\' : \'\';\n"
	"        foreach ($this->tags as $name => $val) {\n"
	"            $close                      = !isset($val[\'close\']) || $val[\'close\'] ? 1 : 0;\n"
	"            $tags[$close][$lib . $name] = $name;\n"
	"            if (isset($val[\'alias\'])) {\n"
	"                // 别名设置\n"
	"                $array = (array) $val[\'alias\'];\n"
	"                foreach (explode(\',\', $array[0]) as $v) {\n"
	"                    $tags[$close][$lib . $v] = $name;\n"
	"                }\n"
	"            }\n"
	"        }\n"
	"\n"
	"        // 闭合标签\n"
	"        if (!empty($tags[1])) {\n"
	"            $nodes = [];\n"
	"            $regex = $this->getRegex(array_keys($tags[1]), 1);\n"
	"            if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {\n"
	"                $right = [];\n"
	"                foreach ($matches as $match) {\n"
	"                    if (\'\' == $match[1][0]) {\n"
	"                        $name = strtolower($match[2][0]);\n"
	"                        // 如果有没闭合的标签头则取出最后一个\n"
	"                        if (!empty($right[$name])) {\n"
	"                            // $match[0][1]为标签结束符在模板中的位置\n"
	"                            $nodes[$match[0][1]] = [\n"
	"                                \'name\'  => $name,\n"
	"                                \'begin\' => array_pop($right[$name]), // 标签开始符\n"
	"                                \'end\'   => $match[0], // 标签结束符\n"
	"                            ];\n"
	"                        }\n"
	"                    } else {\n"
	"                        // 标签头压入栈\n"
	"                        $right[strtolower($match[1][0])][] = $match[0];\n"
	"                    }\n"
	"                }\n"
	"                unset($right, $matches);\n"
	"                // 按标签在模板中的位置从后向前排序\n"
	"                krsort($nodes);\n"
	"            }\n"
	"\n"
	"            $break = \'<!--###break###--!>\';\n"
	"            if ($nodes) {\n"
	"                $beginArray = [];\n"
	"                // 标签替换 从后向前\n"
	"                foreach ($nodes as $pos => $node) {\n"
	"                    // 对应的标签名\n"
	"                    $name  = $tags[1][$node[\'name\']];\n"
	"                    $alias = $lib . $name != $node[\'name\'] ? ($lib ? strstr($node[\'name\'], $lib) : $node[\'name\']) : \'\';\n"
	"                    // 解析标签属性\n"
	"                    $attrs  = $this->parseAttr($node[\'begin\'][0], $name, $alias);\n"
	"                    $method = \'tag\' . $name;\n"
	"                    // 读取标签库中对应的标签内容 replace[0]用来替换标签头，replace[1]用来替换标签尾\n"
	"                    $replace = explode($break, $this->$method($attrs, $break));\n"
	"                    if (count($replace) > 1) {\n"
	"                        while ($beginArray) {\n"
	"                            $begin = end($beginArray);\n"
	"                            // 判断当前标签尾的位置是否在栈中最后一个标签头的后面，是则为子标签\n"
	"                            if ($node[\'end\'][1] > $begin[\'pos\']) {\n"
	"                                break;\n"
	"                            } else {\n"
	"                                // 不为子标签时，取出栈中最后一个标签头\n"
	"                                $begin = array_pop($beginArray);\n"
	"                                // 替换标签头部\n"
	"                                $content = substr_replace($content, $begin[\'str\'], $begin[\'pos\'], $begin[\'len\']);\n"
	"                            }\n"
	"                        }\n"
	"                        // 替换标签尾部\n"
	"                        $content = substr_replace($content, $replace[1], $node[\'end\'][1], strlen($node[\'end\'][0]));\n"
	"                        // 把标签头压入栈\n"
	"                        $beginArray[] = [\'pos\' => $node[\'begin\'][1], \'len\' => strlen($node[\'begin\'][0]), \'str\' => $replace[0]];\n"
	"                    }\n"
	"                }\n"
	"                while ($beginArray) {\n"
	"                    $begin = array_pop($beginArray);\n"
	"                    // 替换标签头部\n"
	"                    $content = substr_replace($content, $begin[\'str\'], $begin[\'pos\'], $begin[\'len\']);\n"
	"                }\n"
	"            }\n"
	"        }\n"
	"        // 自闭合标签\n"
	"        if (!empty($tags[0])) {\n"
	"            $regex   = $this->getRegex(array_keys($tags[0]), 0);\n"
	"            $content = preg_replace_callback($regex, function ($matches) use (&$tags, &$lib) {\n"
	"                // 对应的标签名\n"
	"                $name  = $tags[0][strtolower($matches[1])];\n"
	"                $alias = $lib . $name != $matches[1] ? ($lib ? strstr($matches[1], $lib) : $matches[1]) : \'\';\n"
	"                // 解析标签属性\n"
	"                $attrs  = $this->parseAttr($matches[0], $name, $alias);\n"
	"                $method = \'tag\' . $name;\n"
	"                return $this->$method($attrs, \'\');\n"
	"            }, $content);\n"
	"        }\n"
	"        return;\n"
	"    }\n"
	"\n"
	"    /**\n"
	"     * 按标签生成正则\n"
	"     * @access private\n"
	"     * @param  array|string     $tags 标签名\n"
	"     * @param  boolean          $close 是否为闭合标签\n"
	"     * @return string\n"
	"     */\n"
	"    public function getRegex($tags, $close)\n"
	"    {\n"
	"        $begin   = $this->tpl->config(\'taglib_begin\');\n"
	"        $end     = $this->tpl->config(\'taglib_end\');\n"
	"        $single  = strlen(ltrim($begin, \'\\\\\')) == 1 && strlen(ltrim($end, \'\\\\\')) == 1 ? true : false;\n"
	"        $tagName = is_array($tags) ? implode(\'|\', $tags) : $tags;\n"
	"        if ($single) {\n"
	"            if ($close) {\n"
	"                // 如果是闭合标签\n"
	"                $regex = $begin . \'(?:(\' . $tagName . \')\\b(?>[^\' . $end . \']*)|\\/(\' . $tagName . \'))\' . $end;\n"
	"            } else {\n"
	"                $regex = $begin . \'(\' . $tagName . \')\\b(?>[^\' . $end . \']*)\' . $end;\n"
	"            }\n"
	"        } else {\n"
	"            if ($close) {\n"
	"                // 如果是闭合标签\n"
	"                $regex = $begin . \'(?:(\' . $tagName . \')\\b(?>(?:(?!\' . $end . \').)*)|\\/(\' . $tagName . \'))\' . $end;\n"
	"            } else {\n"
	"                $regex = $begin . \'(\' . $tagName . \')\\b(?>(?:(?!\' . $end . \').)*)\' . $end;\n"
	"            }\n"
	"        }\n"
	"        return \'/\' . $regex . \'/is\';\n"
	"    }\n"
	"\n"
	"    /**\n"
	"     * 分析标签属性 正则方式\n"
	"     * @access public\n"
	"     * @param string $str 标签属性字符串\n"
	"     * @param string $name 标签名\n"
	"     * @param string $alias 别名\n"
	"     * @return array\n"
	"     */\n"
	"    public function parseAttr($str, $name, $alias = \'\')\n"
	"    {\n"
	"        $regex  = \'/\\s+(?>(?P<name>[\\w-]+)\\s*)=(?>\\s*)([\\\"\\\'])(?P<value>(?:(?!\\\\2).)*)\\\\2/is\';\n"
	"        $result = [];\n"
	"        if (preg_match_all($regex, $str, $matches)) {\n"
	"            foreach ($matches[\'name\'] as $key => $val) {\n"
	"                $result[$val] = $matches[\'value\'][$key];\n"
	"            }\n"
	"            if (!isset($this->tags[$name])) {\n"
	"                // 检测是否存在别名定义\n"
	"                foreach ($this->tags as $key => $val) {\n"
	"                    if (isset($val[\'alias\'])) {\n"
	"                        $array = (array) $val[\'alias\'];\n"
	"                        if (in_array($name, explode(\',\', $array[0]))) {\n"
	"                            $tag           = $val;\n"
	"                            $type          = !empty($array[1]) ? $array[1] : \'type\';\n"
	"                            $result[$type] = $name;\n"
	"                            break;\n"
	"                        }\n"
	"                    }\n"
	"                }\n"
	"            } else {\n"
	"                $tag = $this->tags[$name];\n"
	"                // 设置了标签别名\n"
	"                if (!empty($alias) && isset($tag[\'alias\'])) {\n"
	"                    $type          = !empty($tag[\'alias\'][1]) ? $tag[\'alias\'][1] : \'type\';\n"
	"                    $result[$type] = $alias;\n"
	"                }\n"
	"            }\n"
	"            if (!empty($tag[\'must\'])) {\n"
	"                $must = explode(\',\', $tag[\'must\']);\n"
	"                foreach ($must as $name) {\n"
	"                    if (!isset($result[$name])) {\n"
	"                        throw new Exception(\'tag attr must:\' . $name);\n"
	"                    }\n"
	"                }\n"
	"            }\n"
	"        } else {\n"
	"            // 允许直接使用表达式的标签\n"
	"            if (!empty($this->tags[$name][\'expression\'])) {\n"
	"                static $_taglibs;\n"
	"                if (!isset($_taglibs[$name])) {\n"
	"                    $_taglibs[$name][0] = strlen($this->tpl->config(\'taglib_begin_origin\') . $name);\n"
	"                    $_taglibs[$name][1] = strlen($this->tpl->config(\'taglib_end_origin\'));\n"
	"                }\n"
	"                $result[\'expression\'] = substr($str, $_taglibs[$name][0], -$_taglibs[$name][1]);\n"
	"                // 清除自闭合标签尾部/\n"
	"                $result[\'expression\'] = rtrim($result[\'expression\'], \'/\');\n"
	"                $result[\'expression\'] = trim($result[\'expression\']);\n"
	"            } elseif (empty($this->tags[$name]) || !empty($this->tags[$name][\'attr\'])) {\n"
	"                throw new Exception(\'tag error:\' . $name);\n"
	"            }\n"
	"        }\n"
	"        return $result;\n"
	"    }\n"
	"\n"
	"    /**\n"
	"     * 解析条件表达式\n"
	"     * @access public\n"
	"     * @param  string $condition 表达式标签内容\n"
	"     * @return string\n"
	"     */\n"
	"    public function parseCondition($condition)\n"
	"    {\n"
	"        if (strpos($condition, \':\')) {\n"
	"            $condition = \' \' . substr(strstr($condition, \':\'), 1);\n"
	"        }\n"
	"        $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition);\n"
	"        $this->tpl->parseVar($condition);\n"
	"        // $this->tpl->parseVarFunction($condition); // XXX: 此句能解析表达式中用|分隔的函数，但表达式中如果有|、||这样的逻辑运算就产生了歧异\n"
	"        return $condition;\n"
	"    }\n"
	"\n"
	"    /**\n"
	"     * 自动识别构建变量\n"
	"     * @access public\n"
	"     * @param string $name 变量描述\n"
	"     * @return string\n"
	"     */\n"
	"    public function autoBuildVar(&$name)\n"
	"    {\n"
	"        $flag = substr($name, 0, 1);\n"
	"        if (\':\' == $flag) {\n"
	"            // 以:开头为函数调用，解析前去掉:\n"
	"            $name = substr($name, 1);\n"
	"        } elseif (\'$\' != $flag && preg_match(\'/[a-zA-Z_]/\', $flag)) {\n"
	"            // XXX: 这句的写法可能还需要改进\n"
	"            // 常量不需要解析\n"
	"            if (defined($name)) {\n"
	"                return $name;\n"
	"            }\n"
	"            // 不以$开头并且也不是常量，自动补上$前缀\n"
	"            $name = \'$\' . $name;\n"
	"        }\n"
	"        $this->tpl->parseVar($name);\n"
	"        $this->tpl->parseVarFunction($name);\n"
	"        return $name;\n"
	"    }\n"
	"\n"
	"    /**\n"
	"     * 获取标签列表\n"
	"     * @access public\n"
	"     * @return array\n"
	"     */\n"
	"    // 获取标签定义\n"
	"    public function getTags()\n"
	"    {\n"
	"        return $this->tags;\n"
	"    }\n"
	"}\n"
	"\n"
;
#endif